package de.akabahn.ebd.stw.zdxfc.prs.commande;

import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.HILFSHANDLUNG_PREFIX;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.BGTASK_DELAY;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.BGTASK_INITIAL_DELAY;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.GRID_LAYOUT_COLS;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.GRID_LAYOUT_ROWS;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.WINDOW_BG_COLOR;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.WINDOW_SIZE_HEIGHT;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.WINDOW_SIZE_WIDTH;
import static de.akabahn.ebd.stw.zdxfc.prs.commande.ZDXFCCommandeConstants.WINDOW_TITLE;

import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.sound.sampled.Clip;

import org.apache.log4j.Logger;

import de.akabahn.ebd.dbd.DBDAdapter;
import de.akabahn.ebd.dbd.DBDAdapterFactory;
import de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants;
import de.akabahn.ebd.stw.zdxfc.prs.commande.elemente.HilfshandlungsDialog;
import de.akabahn.ebd.stw.zdxfc.prs.commande.elemente.KontrollLampe;
import de.akabahn.ebd.stw.zdxfc.prs.commande.elemente.Schalter;
import de.akabahn.ebd.stw.zdxfc.prs.commande.elemente.Taste;

/**
 * Kontrollklasse zum Starten, Stoppen und Restarten des Bedienpults, Schnittstelle
 * zum DBD und Verwaltung aller Bedien-Elemente.
 * 
 * @author Peter Eimann
 * @since 1.0
 * @version 1.2
 * @see DBDAdapter
 * @see ZDXFCCommandeDispatcher
 * @see ZDXFCCommandeInitializer
 * @see ZDXFCCommandeConstants
 * @see ZDXFCConstants
 */
public class ZDXFCCommandeMain extends Frame implements Observer {

	public final static String VERSION = "2.3.0_1";

	private static final long serialVersionUID = -6827257754622377241L;
	private static final Logger LOG = Logger.getLogger(ZDXFCCommandeMain.class);
	
	private DBDAdapter dbd;
	private Map<String, Schalter> schalter;
	private Map<String, Taste> tasten;
	private Map<String, KontrollLampe> kontrollLampen;
	
	private ZDXFCCommandeDispatcher dispatcher;
	private ZDXFCCommandeInitializer initializer;
	private ScheduledExecutorService backgroundService;
	private HilfshandlungsDialog hilfshandlungsDialog;
	
	private boolean demoMode;
	private boolean hilfsHandlungen;
	private int gesamtZaehler;
	
	private Clip annonceClip;
	private Clip sonnerieClip;

	/**
	 * Initialisierung der Klasse
	 *
	 * @param hilfsHandlungen <code>true</code> - Taste fuer die Hilfshandlungen wird eingeblendet
	 * @param demoMode <code>true</code> - Start der Anwendung im Demo-Mode ohne DBD-Anbindung
	 */
	public ZDXFCCommandeMain(boolean hilfsHandlungen, boolean demoMode) {
		dbd = DBDAdapterFactory.getImplementation(this);
		schalter = new HashMap<String, Schalter>();
		tasten = new HashMap<String, Taste>();
		kontrollLampen = new HashMap<String, KontrollLampe>();
		
		dispatcher = new ZDXFCCommandeDispatcher(this);
		initializer = new ZDXFCCommandeInitializer(this, dispatcher); 
		backgroundService = Executors.newSingleThreadScheduledExecutor();
		hilfshandlungsDialog = new HilfshandlungsDialog(this, "Hilfshandlung", true);
		
		this.demoMode = demoMode;
		this.hilfsHandlungen = hilfsHandlungen;
		LOG.info("Hilfshandlungen allowed = " + hilfsHandlungen);
		LOG.info("Running in DemoMode = " + demoMode);
	}
	
	/**
	 * Hoch- und geordnetes Runterfahren des Bedienpults in der folgenden Reihenfolge:<br>
	 * <ol>
	 * <li>Starten des DBD-Adapters (wenn die Anwendung nicht im Demo-Mode laeuft)</li>
	 * <li>Initialisierung des Fensters</li>
	 * <li>Starten des Background-Tasks (Scheduling mit regelmaessiger Ausfuehrung)</li>
	 * </ol>
	 * @see DBDAdapter#start()
	 * @see ZDXFCCommandeBackgroundTask#run()
	 * @see #initWindow()
	 */
	public void start() throws Exception {
		if (!demoMode) {
			dbd.start();
		}
		
		initWindow();

		Runnable backgroundTask = new ZDXFCCommandeBackgroundTask(this);
		backgroundService.scheduleWithFixedDelay(backgroundTask, BGTASK_INITIAL_DELAY, BGTASK_DELAY, TimeUnit.MILLISECONDS);

		System.out.println("Chateauxprix Commande up and running (Release " + VERSION + ")");
	}
	
	/**
	 * Zugriff auf den DBD-Adapter
	 * 
	 * @return DBD-Adapter
	 */
	public DBDAdapter getDbd() {
		return dbd;
	}

	/**
	 * Zugriff auf den Hilfshandlungs-Dialog
	 * 
	 * @return Hilfshandlungs-Dialog
	 */
	public HilfshandlungsDialog getHilfshandlungsDialog() {
		return hilfshandlungsDialog;
	}

	/**
	 * Erhoeht den Gesamtzaehler um eins und ersetzt das Label auf der Hilfshandlungs-Taste durch den neuen Wert
	 */
	public void increaseGesamtZaehler() {
		gesamtZaehler++;
		getTaste(HILFSHANDLUNG_PREFIX).setLabel(new DecimalFormat("0000").format(gesamtZaehler));
	}
	
	/**
	 * Zugriff auf alle Schalter
	 * 
	 * @return Schalter (Key ist die ID des Schalters)
	 */
	public Map<String, Schalter> getSchalter() {
		return schalter;
	}
	
	/**
	 * Zugriff auf den Schalter mit der uebergebenen ID
	 * 
	 * @param id Die ID des Schalters
	 * @return Schalter mit der ID (oder <code>null</code> wenn die ID nicht gefunden wurde)
	 */
	public Schalter getSchalter(String id) {
		return getSchalter().get(id);
	}

	/**
	 * Hinzufuegen eines Schalters zur Schalter-Map
	 * 
	 * @param schalter Der Schalter, der hinzugefuegt werden soll (als Key wird die ID des Schalters verwendet)
	 */
	public void addSchalter(Schalter schalter) {
		getSchalter().put(schalter.getId(), schalter);
	}
	
	/**
	 * Zugriff auf alle Tasten
	 * 
	 * @return Tasten (Key ist die ID der Taste)
	 */
	public Map<String, Taste> getTasten() {
		return tasten;
	}

	/**
	 * Zugriff auf die Taste mit der uebergebenen ID
	 * 
	 * @param id Die ID der Taste
	 * @return Taste mit der ID (oder <code>null</code> wenn die ID nicht gefunden wurde)
	 */
	public Taste getTaste(String id) {
		return getTasten().get(id);
	}

	/**
	 * Hinzufuegen einer Taste zur Tasten-Map
	 * 
	 * @param taste Die Taste, die hinzugefuegt werden soll (als Key wird die ID der Taste verwendet)
	 */
	public void addTaste(Taste taste) {
		getTasten().put(taste.getId(), taste);
	}
	
	/**
	 * Zugriff auf alle Kontroll-Lampe
	 * 
	 * @return Kontroll-Lampen (Key ist die ID der Lampen)
	 */
	public Map<String, KontrollLampe> getKontrollLampen() {
		return kontrollLampen;
	}

	/**
	 * Zugriff auf die Kontroll-Lampe mit der uebergebenen ID
	 * 
	 * @param id Die ID der Kontroll-Lampe
	 * @return Kontroll-Lampe mit der ID (oder <code>null</code> wenn die ID nicht gefunden wurde)
	 */
	public KontrollLampe getKontrolllampe(String id) {
		return getKontrollLampen().get(id);
	}

	/**
	 * Hinzufuegen einer Kontroll-Lampe zur Kontroll-Lampen-Map
	 * 
	 * @param kontrollLampe Die Kontroll-Lampe, die hinzugefuegt werden soll (als Key wird die ID der Kontroll-Lampe
	 *                      verwendet)
	 */
	public void addKontrollLampe(KontrollLampe kontrollLampe) {
		getKontrollLampen().put(kontrollLampe.getId(), kontrollLampe);
	}
	
	/**
	 * Gibt zurueck, ob die Anwendung im Demo-Mode laeuft oder nicht
	 * 
	 * @return <code>true</code> - Die Anwendung laeuft im Demo-Mode
	 */
	public boolean isDemoMode() {
		return demoMode;
	}

	/**
	 * Gibt zurueck, ob die Taste fuer die Hilfshandlungen dargestellt wird oder nicht
	 * 
	 * @return <code>true</code> - Die Taste wird dargestellt
	 */
	public boolean isHilfsHandlungen() {
		return hilfsHandlungen;
	}

	/**
	 * Setzen des Annonce-Clip
	 * @param annonceClip
	 */
	public void setAnnonceClip(Clip annonceClip) {
		this.annonceClip = annonceClip;
	}

	/**
	 * Zugriff auf den Annonce-Clip
	 * @return
	 */
	public Clip getAnnonceClip() {
		return annonceClip;
	}

	/**
	 * Setzen des Sonnerie-Clip
	 * @param sonnerieClip
	 */
	public void setSonnerieClip(Clip sonnerieClip) {
		this.sonnerieClip = sonnerieClip;
	}

	/**
	 * Zugriff auf den Sonnerie-Clip
	 * @return
	 */
	public Clip getSonnerieClip() {
		return sonnerieClip;
	}

	/**
	 * Initialisierung des Fensters des Bedienpults.<br>
	 * Setzt den Titel, das Layout (GridLayout), die Hintergrundfarbe, die Groesse und fuegt alle Kontroll-Elemente
	 * hinzu. Das Fenster ist nicht skalierbar.<br>
	 * Wenn die Anwendung nicht im Demo-Mode laeuft, werden die verwendeten DBD-Variablen abonniert.
	 * 
	 * @see ZDXFCCommandeInitializer#initialize()
	 * @see ZDXFCCommandeInitializer#abonniereElemente()
	 */
	private void initWindow() throws Exception {
		addWindowListener(new ZDXFCWindowListener(dbd));
		
		setTitle(WINDOW_TITLE);
		setLayout(new GridLayout(GRID_LAYOUT_ROWS, GRID_LAYOUT_COLS));
		setBackground(WINDOW_BG_COLOR);
		setSize(WINDOW_SIZE_WIDTH, WINDOW_SIZE_HEIGHT);
		setResizable(false);
		
		initializer.initialize();
		setVisible(true);

		if (!demoMode) {
			initializer.abonniereElemente();
		}
	}
	
	/**
	 * Window-Listener zum geordneten Schliessen der Anwendung. Vor dem Beenden der Anwendung wird der DBD-Adapter
	 * gestoppt.<br>
	 * Der Listener reagiert nur auf das Event <code>windowClosing()</code>.
	 */
	private static class ZDXFCWindowListener extends WindowAdapter {
		private DBDAdapter dbd;

		public ZDXFCWindowListener(DBDAdapter dbd) {
			this.dbd = dbd;
		}
		
		@Override
		public void windowClosing(WindowEvent e) {
			if (dbd.isStarted()) {
				dbd.stop();
			}
			
			e.getWindow().dispose();
			System.exit(0);
		}
	}

	/**
	 * Verbindungsinformation des DBD-Adapters, wird beim Wechsel des Verbindungsstatus aufgerufen
	 * 
	 * @param observable Das Adapter-Objekt
	 * @param object Zustand der Verbindung (true, false)
	 */
	@Override
	public void update(Observable observable, Object object) {
		LOG.warn("Connected to DBD: " + object.toString());
	}
}
